/**
 * @Author: xzw
 * @Description: 兼容go crypto实现的基础定义
 * @Version: 1.0.0
 * @Date: 2021/3/2 下午11:04
 * @Copyright: MIN-Group；国家重大科技基础设施——未来网络北大实验室；深圳市信息论与未来网络重点实验室
 */


const HashType = {
	SM3        :1,
	MD5        :2,
	SHA256     :3,
	SHA512     :4,
	maxHash    :5,
}
const sm2 = require("sm-crypto").sm2;
// const sm2 = require("./crypto/sm2/external");
const uti = require("./crypto/utils")

var digestSizes = new Map([[HashType.SM3, 32], [HashType.MD5, 16], [HashType.SHA256, 32], [HashType.SHA512, 64]]);

const crypto = require('crypto');
const sm3 = require("sm-crypto").sm3;
const crypto_md5 = crypto.createHash('md5');
const crypto_sha256 = crypto.createHash('sha256');
const crypto_sha512 = crypto.createHash('sha512');

var hashes = [sm3, md5, sha256, sha512]

function md5(msg){
	crypto_md5.update(msg)
	let hashdata = crypto_md5.digest('hex')
	return hashdata
}

function sha256(msg){
	crypto_sha256.update(msg)
	let hashdata = crypto_sha256.digest('hex')
	return hashdata
}

function sha512(msg){
	crypto_sha512.update(msg)
	let hashdata = crypto_sha512.digest('hex')
	return hashdata
}
// Hash identifies a cryptographic hash function that is implemented in another
// package.
class Hash {
	constructor(h) 
    {
        this.h = h
    }
    
	// HashFunc simply returns the value of h so that Hash implements SignerOpts.
	HashFunc() {
		return this.h
	}
	// Size returns the length, in bytes, of a digest resulting from the given hash
    // function. It doesn't require that the hash function in question be linked
    // into the program.
	Size() {
		if (this.h > 0 && this.h < HashType.maxHash) {
			return digestSizes.get(this.h)
		}
		// panic("crypto: Size of unknown hash function")
	}
	// New returns a new hash.Hash calculating the given hash function. New panics
    // if the hash function is not linked into the binary.
	New()  {
		if (this.h > 0 && this.h < HashType.maxHash) {
			var hash = hashes[this.h-1]
			// if (this.h === 1){
			// 	hash = sm3
			// }
			// else if (this.h === 2){
			// 	hash = md5
			// }
			// else if (this.h === 3){
			// 	hash = sha256
			// }
			// else if (this.h === 4){
			// 	hash = sha512
			// }
			return hash
		}
		panic("crypto: requested hash function #" + strconv.Itoa(int(h)) + " is unavailable")
	}
	// Available reports whether the given hash function is linked into the binary.
	Available() {
		return this.h < HashType.maxHash && hashes[this.h-1] != null
	}
}


// RegisterHash registers a function that returns a new instance of the given
// hash function. This is intended to be called from the init function in
// packages that implement hash functions.
function RegisterHash(h , f) {
    let hash = new Hash(h)
	if (hash.h >= maxHash) {
		panic("crypto: RegisterHash of unknown hash function")
	}
	hashes[h-1] = f
}


// PublicKey represents a public key using an unspecified algorithm.
//函数的参数是兼容go crypto的实现，在国密中*Opts都是nil处理
class PublicKey {
    constructor(pub) 
    {
        this.PublicKey = pub
    }
    //
    // 获取公钥的字节表示
    //
    // @Description:
    // @return []byte
    //
    GetBytes() {
        const uti = require("./crypto/utils")
        return uti.Str2Bytes(this.PublicKey)
    }

    //
    // 根据所给字节设置公钥
    //
    // @Description:
    // @param pubBytes []byte
    // @return bool
    //
    SetBytes(pubBytes) {
        const uti = require("./crypto/utils")
        if (uti.Bytes2Str(pubBytes).length != 130 ){
            return new Error("设置公钥长度错误").message
        }
        try{
            let data = uti.Bytes2Str(pubBytes)
            let x = data.substring(2,66).toString(16)
            let y = data.substring(66,130).toString(16)
            this.PublicKey = '04' + x + y
        }catch(err){
            return err.message
        }
        return null
    }

    //
    // 公钥加密
    //
    // @Description:
    // @param msg []byte
    // @param opt com.DecrypterOpts
    // @return encryptedtext []byte
    // @return err error
    //
    Encrypt(msg, opts)  {
        const uti = require("./crypto/utils")
        let cipherMode = opts
        msg = uti.Bytes2Str(msg)
        try {
            return [uti.Str2Bytes(sm2.doEncrypt(msg, this.PublicKey, cipherMode)), null]
        }catch(err){
            return [null, err.message]
        }
    }

    //
    // 公钥验证
    //
    // @Description:
    // @param msg []byte
    // @param sign []byte
    // @param opt com.SignerOpts
    // @return bool 
    // @return error
    //
    Verify(msg, sign, opts)  {
        const uti = require("./crypto/utils")
        msg = uti.Bytes2Str(msg)
        try{
            return [sm2.doVerifySignature(msg, sign, this.PublicKey), null]
        }catch(err){
            return [null, err.message]
        }
    }
}

// PrivateKey represents a private key using an unspecified algorithm.
class PrivateKey {
	constructor(prk) 
    {
        this.PrivateKey = prk
    }
    //
    // 获取私钥的字节表示
    //
    // @Description:
    // @return []byte
    //
    GetBytes() {
        const uti = require("./crypto/utils")
        return uti.Str2Bytes(this.PrivateKey)
    }

    //
    // 根据所给字节设置私钥
    //
    // @Description:
    // @param priBytes []byte
    // @return bool
    //
    SetBytes(priBytes) {
        const uti = require("./crypto/utils")
        if (uti.Bytes2Str(priBytes).length !=64) {
            return new Error("设置私钥长度错误").message
        }
        let pri = uti.Bytes2Str(priBytes)

        this.PrivateKey = pri
        return null
    }

    //
    // 私钥解密
    //
    // @Description:
    // @param msg []byte
    // @param opt com.DecrypterOpts
    // @return plaintext []byte
    // @return err error
    //
    Decrypt(msg, opts)  {
        const uti = require("./crypto/utils")
        let cipherMode = opts
        msg = uti.Bytes2Str(msg)
        try {
            return [uti.Str2Bytes(sm2.doDecrypt(msg, this.PrivateKey, cipherMode)), null]
        }catch(err){
            return [null, err.message]
        }
    }

    //
    // 私钥签名
    //
    // @Description:
    // @param digest []byte
    // @param opt com.SignerOpts
    // @return plaintext []byte
    // @return err error
    //
    Sign(digest, opts) {
        const uti = require("./crypto/utils")
        digest = uti.Bytes2Str(digest)
        try{
            return [sm2.doSignature(digest, this.PrivateKey), null]
        }catch(err){
            return [null, err.message]
        }
    }
}

// SignerOpts contains options for signing with a Signer.
class SignerOpts extends Hash {
	constructor(h) 
    {
        super(h)
    }
	// HashFunc returns an identifier for the hash function used to produce
	// the message passed to Signer.Sign, or else zero to indicate that no
	// hashing was done.
}

class DecrypterOpts {}

module.exports = {
    HashType: HashType,
    digestSizes: digestSizes,
	hashes: hashes,
    Hash: Hash,
	RegisterHash: RegisterHash,
	PublicKey: PublicKey,
	PrivateKey: PrivateKey,
	SignerOpts: SignerOpts
}